﻿namespace nGo.Web.Mvc

open System
open System.Collections.Generic
open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
open System.Configuration
open System.Data.Entity
open System.Data.Entity.Infrastructure
open System.Diagnostics
open System.Linq
open System.Web
open System.Web.Http
open System.Web.Mvc
open System.Web.Optimization
open System.Web.Routing

module public Globals = begin
    let public GetBundles () = BundleTable.Bundles
    let public GetFilters () = GlobalFilters.Filters
    let public GetRoutes () = RouteTable.Routes
    let public GetDependencyResolver () = DependencyResolver.Current
    let public GetDefaultDatabaseConnectionFactory () = Database.DefaultConnectionFactory
    let public GetControllerBuilder () = ControllerBuilder.Current
    let public GetFilterProviders () = FilterProviders.Providers
    let public GetModelValidatorProviders () = ModelValidatorProviders.Providers
    let public GetModelBinderProviders () = ModelBinderProviders.BinderProviders
    let public GetBundleResolver () = BundleResolver.Current
    let public GetHttpContext () = HttpContext.Current
end

module public Routes = begin
    /// Represents an HTTP route for Web API routing.
    type public HttpRoute = { id : RouteParameter }

    /// Represents a Controller route for MVC routing.
    type public ControllerRoute = { controller : string; action : string; id : UrlParameter }
    
    let internal ignoreRoute path (routes:RouteCollection) = 
        routes.IgnoreRoute(path)
        routes

    let internal mapHttpRoute name path (route:HttpRoute) (routes:RouteCollection) = 
        routes.MapHttpRoute(name, path, route) |> ignore
        routes

    let internal mapControllerRoute name path (route:ControllerRoute) (routes:RouteCollection) = 
        routes.MapRoute(name, path, route) |> ignore
        routes
end

module public Content = begin
    [<AbstractClass>]
    type public Bundles<'a when 'a :> Bundles<'a>>() =
        abstract member RegisterTemplateBundles : unit -> 'a
        abstract member Count : int with get

    type public DefaultBundles() =
        inherit Bundles<DefaultBundles>()

        override x.RegisterTemplateBundles() = Globals.GetBundles().RegisterTemplateBundles(); x

        override x.Count with get() = Globals.GetBundles().Count
    
    let internal registerBundleTemplates<'a when 'a :> Bundles<'a>> (bundles:Bundles<'a>) = bundles.RegisterTemplateBundles()
    
    let internal registerBundleResolver (resolver:IBundleResolver) = 
        BundleResolver.Current <- resolver
        resolver
end

module public Areas = begin
    let public DefaultAreaRegistration = nGo.SingletonAction((fun() -> AreaRegistration.RegisterAllAreas()), false)
    
    /// Registers areas once and only once as a singleton.
    /// The function parameter "areaRegistration" should return true if the areas were successfully registered; otherwise false.
    /// This function can continue to attempt to register areas if they have never been successfully registered.
    let internal registerAreas (areaRegistration:nGo.SingletonAction) = areaRegistration.Invoke()
end

module public Databases = begin
    let public DefaultConnectionFactory = nGo.SingletonDelegate((fun() -> Database.DefaultConnectionFactory), (fun(value) -> Database.DefaultConnectionFactory <- value))
    
    /// Registers the default DB if they are not already registered.
    let internal setDefaultDb connectionFactory = 
        DefaultConnectionFactory.Value <- connectionFactory
        connectionFactory
end

module public Filters = begin
    let public addFilter filter (filters:GlobalFilterCollection) = 
        filters.Add(filter)
        filters

    let internal removeDefaultFilterProvider () =
        let providers = Globals.GetFilterProviders()
        let defaultFilterProvider = providers |> Seq.find (fun provider -> provider :? FilterAttributeFilterProvider)
        providers.Remove(defaultFilterProvider) |> ignore
        defaultFilterProvider

    let internal registerFilterProvider (filterProvider:IFilterProvider) =
        Globals.GetFilterProviders().Add(filterProvider)
        filterProvider

    let internal replaceDefaultFilterProvider (filterProvider:IFilterProvider) =
        removeDefaultFilterProvider |> ignore
        registerFilterProvider filterProvider
end

module public Dependencies = begin
    let internal registerDependencyResolver (resolver:IDependencyResolver) = 
        DependencyResolver.SetResolver(resolver)
        resolver
end

module public Controllers = begin
    open nGo.Models

    let internal registerControllerFactory (factory:IControllerFactory) = 
        Globals.GetControllerBuilder().SetControllerFactory(factory)
        factory

    type ControllerBase(modelFactory:ModelFactory) =
        inherit Controller()

        let internalModelFactory = modelFactory

        new() = new ControllerBase(ModelFactory())

        member x.HandleViewAction<'TModel when 'TModel : not struct> () =
            let model = internalModelFactory.GetModel<'TModel>()
            x.View(model)

        member x.HandleViewAction<'TModel when 'TModel : not struct> (view:string) =
            let model = internalModelFactory.GetModel<'TModel>()
            x.View(view, model)

        member x.HandleViewAction<'TRequest,'TResponse when 'TRequest : not struct and 'TResponse : not struct> (request:'TRequest, view:string) =
            match x.TryValidateModel(request) with
            | true -> x.View(view, internalModelFactory.GetModel<'TRequest, 'TResponse>(request)) :> ActionResult
            | false -> x.HttpNotFound() :> ActionResult
end

module public Models = begin
    let internal removeDefaultModelValidatorProvider () =
        let providers = Globals.GetModelValidatorProviders()
        let defaultModelValidatorProvider = providers |> Seq.find (fun provider -> provider :? DataAnnotationsModelValidatorProvider)
        providers.Remove(defaultModelValidatorProvider) |> ignore
        defaultModelValidatorProvider
    
    let internal registerModelValidatorProvider (provider:ModelValidatorProvider) = 
        Globals.GetModelValidatorProviders().Add(provider)
        provider

    let internal replaceDefaultModelValidatorProvider (provider:ModelValidatorProvider) =
        removeDefaultModelValidatorProvider |> ignore
        registerModelValidatorProvider provider

    let internal registerModelBinderProvider (provider:IModelBinderProvider) =
        Globals.GetModelBinderProviders().Add(provider)
        provider
end

[<AbstractClass>]
type public ApplicationPreStart() =
    abstract member RegisterDependencies : IDependencyResolver -> IDependencyResolver
    abstract member RegisterControllerFactory : IControllerFactory -> IControllerFactory
    abstract member RegisterFilterProvider : IFilterProvider -> IFilterProvider
    abstract member RegisterModelValidatorProvider : ModelValidatorProvider -> ModelValidatorProvider
    abstract member RegisterModelBinderProvider : IModelBinderProvider -> IModelBinderProvider
    abstract member RegisterBundleResolver : IBundleResolver -> IBundleResolver

    default x.RegisterDependencies(resolver:IDependencyResolver) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterDependencies()")
        Dependencies.registerDependencyResolver resolver
            
    default x.RegisterControllerFactory(factory:IControllerFactory) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterControllerFactory()")
        Controllers.registerControllerFactory factory

    default x.RegisterFilterProvider(provider:IFilterProvider) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterFilterProvider()")
        Filters.replaceDefaultFilterProvider provider

    default x.RegisterModelValidatorProvider(provider:ModelValidatorProvider) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterModelValidatorProvider()")
        Models.registerModelValidatorProvider provider
    
    default x.RegisterModelBinderProvider(provider:IModelBinderProvider) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterModelBinderProvider()")
        Models.registerModelBinderProvider provider

    default x.RegisterBundleResolver(resolver:IBundleResolver) = 
        Debug.WriteLine("nGo: ApplicationPreStart.RegisterBundleResolver()")
        Content.registerBundleResolver resolver
    
type public Application() =
    inherit HttpApplication()
        
    static let mutable isStarted = false
    static let startLock = obj()

    override x.Init() =
        Debug.WriteLine("nGo: Application.Init() initialized? {0}", isStarted)
        if not isStarted then
            lock startLock (fun() -> 
                if not isStarted then
                    let started = x.Start()
                    isStarted <- started)

    member x.IsStarted with get() = isStarted

    abstract member Start : unit -> bool
    default x.Start() = 
        Debug.WriteLine("nGo: Application.Start()")

        Debug.WriteLine("nGo: Application.RegisterAreas()")
        x.RegisterAreas() |> ignore

        Debug.WriteLine("nGo: Application.SetDefaultDb()")
        let defaultDatabaseConnectionFactory = new SqlConnectionFactory("Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True")
        x.SetDefaultDb(defaultDatabaseConnectionFactory) |> ignore

        Debug.WriteLine("nGo: Application.AddFilters()")
        x.AddFilters(Globals.GetFilters()) |> ignore

        Debug.WriteLine("nGo: Application.AddRoutes()")
        x.AddRoutes(Globals.GetRoutes()) |> ignore

        Debug.WriteLine("nGo: Application.AddBundles()")
        x.AddBundles(Content.DefaultBundles()) |> ignore

        true

    abstract member RegisterAreas : unit -> bool
    default x.RegisterAreas() = Areas.registerAreas Areas.DefaultAreaRegistration

    abstract member SetDefaultDb : IDbConnectionFactory -> IDbConnectionFactory
    default x.SetDefaultDb(defaultDbConnectionFactory) = Databases.setDefaultDb defaultDbConnectionFactory

    abstract member AddFilters : GlobalFilterCollection -> GlobalFilterCollection
    default x.AddFilters(filters) = Filters.addFilter (HandleErrorAttribute()) filters

    abstract member AddRoutes : RouteCollection -> RouteCollection
    default x.AddRoutes(routes) = 
        let ignore, api, controller = ("{resource}.axd/{*pathInfo}", "api/{controller}/{id}", "{controller}/{action}/{id}")

        Debug.WriteLine("nGo: Routes... Ignore: {0}, API: {1}, Controllers: {2}", ignore, api, controller)

        routes
            |> Routes.ignoreRoute ignore
            |> Routes.mapHttpRoute "DefaultApi" api { Routes.HttpRoute.id = RouteParameter.Optional }
            |> Routes.mapControllerRoute "Default" controller { Routes.ControllerRoute.controller = "Home"; action = "Index"; id = UrlParameter.Optional }

    abstract member AddBundles<'Bundles when 'Bundles :> Content.Bundles<'Bundles>> : 'Bundles -> 'Bundles
    default x.AddBundles(bundles) = Content.registerBundleTemplates bundles
